package assign2;
import java.util.concurrent.Semaphore;

/**
 * Invariants:
 * (empty + full) == (bufferSize)
 * bufferLocks[i] = (0 V 1), for all i
 */
public class assign2_q14 {
	static final int bufferSize = 10; //Size of the main buffer
	static final int producers = 3; //# of producing threads
	static final int consumers = 3; //# of consuming threads 
	
	int buffer[] = new int[bufferSize]; // The main buffer
    Semaphore bufferLocks[] = new Semaphore[bufferSize]; // A set of semaphores for each buffer position
	int front = 0; // Indicates the slot to read from
	int rear = 0; // Indicates the slot to write into
    Semaphore full = new Semaphore(0); // Gets permits when the producer fills a buffer slot and releases
    Semaphore empty = new Semaphore(bufferSize); // Looses permits when producers acquire them
    
    public assign2_q14()
    {
    	for(int i = 0; i < bufferSize; ++i)
    	{
    		bufferLocks[i] = new Semaphore(1); //fills the buffer lock semaphores to indicate empty slots
    		System.out.println("createing buffer semaphore: " + i);
    	}
    	for(int i = 0; i<producers; ++i)
    	{
    		Producer producer = new Producer(i);
    		producer.start();
    	}
    	for(int i = 0; i<consumers; ++i)
    	{
    		Consumer consumer = new Consumer(i);
    		consumer.start();
    	}
    }
    
    public class Producer extends CountedThread
    {	
    	public Producer(int num)
		{
			super(num, "Producer");
		}

		public void run()
    	{
    		while(true)
    		{
    			try 
    			{ 
    				/**
    				 * <await(empty > 0) >
    				 */
    				empty.acquire(); // P(empty) 
    			} 
    			catch (InterruptedException e1) {}
				
    			int product = (int)(Math.random()*1000.0);
    			printCount(product);
    			
    			try 
    			{ 
    				bufferLocks[rear].acquire(); // P(bufferLocks(rear))
    			} 
    			catch (InterruptedException e1) {}
				
    			buffer[rear] = product;
    			bufferLocks[rear].release(); // V(bufferLocks(rear))
    			rear = (rear + 1) % bufferSize;
    			
				/**
				 * <full = full - 1>
				 */
    			full.release(); // V(full)
    			
    			try
				{
					Thread.sleep(500);
				}
				catch (InterruptedException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
    		}
    	}
    }
    
    public class Consumer extends CountedThread
    {	
    	public Consumer(int num)
		{
			super(num, "Consumer");
		}

		public void run()
    	{
    		while(true)
    		{
    			try
    			{
    				/**
    				 * <await(full > 0)>
    				 */
    				full.acquire(); // P(full)
    			}
    			catch(InterruptedException e1) {}
    			
    			int product;
    			
    			try
    			{
    				bufferLocks[front].acquire(); // P(bufferLocks(front))
    			}
    			catch(InterruptedException e1) {}
    			
    			product = buffer[front];
    			bufferLocks[front].release(); // V(bufferLocks(front))
    			front = (front + 1) % bufferSize;
    			printCount(product);
    			
				/**
				 * <empty = empty - 1>
				 */
    			empty.release(); // V(empty)
    			
    			try
				{
					Thread.sleep(500);
				}
				catch (InterruptedException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
    		}
    	}
    }

    class CountedThread extends Thread
    {
    	protected int _num;
    	private String _threadType;
    	
    	public CountedThread(int num, String threadType)
    	{
    		_num = num;
    		_threadType = threadType;
    	}
    	public void printCount(int value)
    	{
    		System.out.println(_threadType + _num + ": " + value);
    	}
    }
    
    public static void main(String[] args)
    {
    	new assign2_q14();
    }
}